/******************************************************************************
 * Copyright (c) 2011 Texas Instruments Incorporated - http://www.ti.com
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *
 *    Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 *    Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the
 *    distribution.
 *
 *    Neither the name of Texas Instruments Incorporated nor the names of
 *    its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *****************************************************************************/

/**************************************************************************************
 * FILE PURPOSE: EEPROM writer utility
 **************************************************************************************
 * FILE NAME: eepromwriter.c
 *
 * DESCRIPTION: A simple EEPROM writer using platform lib APIs to program the EEPROM
 *              with an image that the ibl can read.
 *
 ***************************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include<ctype.h>
#include <string.h>
#include "platform.h"
#include "types.h"

/* EEPROM writer utility version */
char version[] = "01.00.00.05";


/* The input file name is hard coded */
char *input_file = "eepromwriter_input.txt";

/* Parameters defined in the input_file */
#define FILE_NAME      "file_name"
#define BUS_ADDR       "bus_addr"
#define START_ADDR     "start_addr"
#define SWAP_DATA      "swap_data"

/* Memory address to store the write data */
#define WRITE_DATA_ADDRESS     0x0C000000

/******************************************************************************
 * Structure:   EEPROM_WRITER_INFO_T
 *
 *              EEPROM writer control data. This structure should be filled in
 *              by the user before running
 ******************************************************************************/
#define MAX_LINE_LENGTH 40
typedef struct EEPROM_WRITER_INFO_tag
{
    char        file_name[MAX_LINE_LENGTH]; /* CCS format data file name */
    uint32_t    busAddr;                    /* Slave bus address */
    uint32_t    startAddr;                  /* Start address to write */
    uint32_t    swapData;                   /* Swap byte in the 32-bit word of the data */
    uint32_t    deviceTotalBytes;           /* Total number of bytes available in the device */
    uint32_t    writeBytes;                 /* Number of bytes to be written into the device */
    uint8_t     *writeData;                 /* Address to store the write data */
    uint8_t     *readData;                  /* Address to store the read data */

} EEPROM_WRITER_INFO_T;

EEPROM_WRITER_INFO_T eepromWriterInfo;

/* OSAL functions for Platform Library */
uint8_t *Osal_platformMalloc (uint32_t num_bytes, uint32_t alignment)
{
	return malloc(num_bytes);
}

void Osal_platformFree (uint8_t *dataPtr, uint32_t num_bytes)
{
    /* Free up the memory */
    if (dataPtr)
    {
        free(dataPtr);
    }
}

void Osal_platformSpiCsEnter(void)
{
    return;
}

void Osal_platformSpiCsExit (void)
{
    return;
}

/******************************************************************************
 * Function:    print_platform_errno
 ******************************************************************************/
void
print_platform_errno
(
    void
)
{
    printf ("Returned platform error number is %d\n", platform_errno);
}

/******************************************************************************
 * Function:    form_block
 *
 *      Form a block of data to write to the NOR. The block is
 *      created as a byte stream from the 4 byte stream in which
 *      the MSB is always sent first.
 ******************************************************************************/
void
formBlock
(
    uint32_t      *data,
    uint32_t      blockSize,
    uint8_t       *scratch
)
{
    uint32_t i, j;

    /* Convert the data to a byte stream */
    for (i = j = 0; j < blockSize; i++, j+=4)
    {
        scratch[j+0] = (data[i] >> 24) & 0xff;
        scratch[j+1] = (data[i] >> 16) & 0xff;
        scratch[j+2] = (data[i] >>  8) & 0xff;
        scratch[j+3] = (data[i] >>  0) & 0xff;
    }
}

/******************************************************************************
 * Function:    flash_eeprom
 *
 *              Write the data from memory to EEPROM.
 *              Returns TRUE if the data is written successfully
 *                      FALSE if the data write fails
 ******************************************************************************/
Bool
flash_eeprom
(
    PLATFORM_DEVICE_info    *p_device
)
{
    uint8_t       *scrach_block;

    printf ("Writing %d bytes from DSP memory address 0x%08x to EEPROM bus address 0x%04x starting from device address 0x%04x ...\n",
            eepromWriterInfo.writeBytes,
            (uint32_t)eepromWriterInfo.writeData,
            eepromWriterInfo.busAddr,
            eepromWriterInfo.startAddr);

    if (eepromWriterInfo.swapData)
    {
	    scrach_block = malloc(eepromWriterInfo.deviceTotalBytes);
	    if (scrach_block == NULL)
	    {
	        printf ("Can not allocate scratch block memory!\n");
	        return (FALSE);
	    }
        formBlock((uint32_t *)(eepromWriterInfo.writeData), eepromWriterInfo.deviceTotalBytes, scrach_block);
	}
	else
	{
		scrach_block = eepromWriterInfo.writeData;
	}

    if(platform_device_write(p_device->handle, eepromWriterInfo.startAddr, scrach_block, eepromWriterInfo.writeBytes) != Platform_EOK)
    {
        print_platform_errno();
        if (eepromWriterInfo.swapData)
        	free (scrach_block);
        return FALSE;
    }

	if(eepromWriterInfo.swapData)
    	free (scrach_block);

    return TRUE;
}

/******************************************************************************
 * Function:    flash_verify
 *
 *              Read back the data file that was just flashed.
 *              Returns TRUE if the data verified correctly.
 *                      FALSE if the data verification failed
 ******************************************************************************/
Bool
flash_verify
(
    PLATFORM_DEVICE_info    *p_device
)
{
    uint32_t    i, j;
    uint8_t     *scrach_block;
    uint32_t    *read_data_w;

    printf ("Reading %d bytes from EEPROM bus address 0x%04x to DSP memory address 0x%08x starting from device address 0x%04x ...\n",
            eepromWriterInfo.writeBytes,
            eepromWriterInfo.busAddr,
            (uint32_t)eepromWriterInfo.readData,
            eepromWriterInfo.startAddr);

    if (eepromWriterInfo.swapData)
    {
	    scrach_block = malloc(eepromWriterInfo.deviceTotalBytes);
	    if (scrach_block == NULL)
	    {
	        printf ("Can not allocate scratch block memory!\n");
	        return (FALSE);
	    }
	}
	else
	{
		scrach_block = eepromWriterInfo.readData;
	}

    if(platform_device_read(p_device->handle, eepromWriterInfo.startAddr, scrach_block, eepromWriterInfo.writeBytes) != Platform_EOK)
    {
        print_platform_errno();
        return FALSE;
    }

    printf ("Verifying data read ...\n");

    if (eepromWriterInfo.swapData)
    {
        /* Convert the packed data */
        read_data_w = (uint32_t *)(eepromWriterInfo.readData);
        for  (i = 0, j = 0; i < eepromWriterInfo.deviceTotalBytes; i += 4)
            read_data_w[j++] = (scrach_block[i+0] << 24) | (scrach_block[i+1] << 16) | (scrach_block[i+2] << 8) | scrach_block[i+3];
   }


    for (i = 0; i < eepromWriterInfo.writeBytes; i++)
    {
        if (eepromWriterInfo.readData[i] != eepromWriterInfo.writeData[i])
        {
            printf ("Failure at byte %d, expected 0x%08x, read 0x%08x\n", i, eepromWriterInfo.writeData[i], eepromWriterInfo.readData[i]);
            return (FALSE);
        }
    }

    return (TRUE);
}

int32_t 
xtoi
(
    char            *xs, 
    uint32_t        *result
)
{
    uint32_t    szlen = strlen(xs);
    int32_t     i, xv, fact;
    
    if (szlen > 0)
    {
        /* Converting more than 32bit hexadecimal value? */
        if (szlen>8) return 2; /* exit */
        
        /* Begin conversion here */
        *result = 0;
        fact = 1;
        
        /* Run until no more character to convert */
        for(i=szlen-1; i>=0 ;i--)
        {
            if (isxdigit(*(xs+i)))
            {
                if (*(xs+i)>=97)
                {
                    xv = ( *(xs+i) - 97) + 10;
                }
                else if ( *(xs+i) >= 65)
                {
                    xv = (*(xs+i) - 65) + 10;
                }
                else
                {
                    xv = *(xs+i) - 48;
                }
                *result += (xv * fact);
                fact *= 16;
            }
            else
            {
                // Conversion was abnormally terminated
                // by non hexadecimal digit, hence
                // returning only the converted with
                // an error value 4 (illegal hex character)
                return 4;
            }
        }
        return 0;
    }
    
    // Nothing to convert
    return 1;
}


/******************************************************************************
 * Function:    parse_input_file
 ******************************************************************************/
static Bool
parse_input_file
(
    FILE*               fp
)
{
    char line[MAX_LINE_LENGTH];
    char tokens[] = " :=;\n\r";
    char *key, *data;

    memset(line, 0, MAX_LINE_LENGTH);

    fgets(line, MAX_LINE_LENGTH, fp);
    key  = (char *)strtok(line, tokens);
    data = (char *)strtok(NULL, tokens);

    if(strlen(data) == 0)
    {
       return FALSE;
    }

    if(strcmp(key, FILE_NAME) != 0)
    {
        return FALSE;
    }

    strcpy (eepromWriterInfo.file_name, data);

    fgets(line, MAX_LINE_LENGTH, fp);
    key  = (char *)strtok(line, tokens);
    data = (char *)strtok(NULL, tokens);

    if(strlen(data) == 0)
    {
       return FALSE;
    }

    if(strcmp(key, BUS_ADDR) != 0)
    {
        return FALSE;
    }

    if ((data[0] == '0') && (data[1] == 'x' || data[1] == 'X'))
    {
        if (xtoi (&data[2], &eepromWriterInfo.busAddr) != 0)
        {
            return FALSE;
        }
    }
    else
    {
        eepromWriterInfo.busAddr = (uint32_t)atoi(data);
    }

    fgets(line, MAX_LINE_LENGTH, fp);
    key  = (char *)strtok(line, tokens);
    data = (char *)strtok(NULL, tokens);

    if(strlen(data) == 0)
    {
       return FALSE;
    }

    if(strcmp(key, START_ADDR) != 0)
    {
        return FALSE;
    }

    eepromWriterInfo.startAddr = (uint32_t)atoi(data);

    fgets(line, MAX_LINE_LENGTH, fp);
    key  = (char *)strtok(line, tokens);
    data = (char *)strtok(NULL, tokens);

    if(strlen(data) == 0)
    {
       return FALSE;
    }

    if(strcmp(key, SWAP_DATA) != 0)
    {
        return FALSE;
    }

    eepromWriterInfo.swapData = (uint32_t)atoi(data);

    return TRUE;
}

/******************************************************************************
 * Function:    find_file_length
 ******************************************************************************/
static Bool
find_file_length
(
    FILE*               fp
)
{
    char        line[MAX_LINE_LENGTH];
    char        *pEnd;
    char        *ext;
    uint32_t    data_len, write_addr;

    memset(line, 0, MAX_LINE_LENGTH);

    ext = strrchr(eepromWriterInfo.file_name, '.');


    if (ext && (strcmp(ext, ".dat") == 0))
    {
        
        fgets(line, MAX_LINE_LENGTH, fp);
        
        /* Read the write address from the CCS header */
        strtoul (line,&pEnd,16);
        strtoul (pEnd,&pEnd,16);
        write_addr = strtoul (pEnd,&pEnd,16);
        strtoul (pEnd,&pEnd,16);
        
        /* Read the data length */
        data_len = (strtoul (pEnd,NULL,16)) * 4;
    }
    else
    {
        /* find the data length by seeking to the end and getting position */
        fseek(fp, 0, SEEK_END);
        data_len = ftell(fp);
        fseek(fp, 0, SEEK_SET);
    }

    if (data_len > (eepromWriterInfo.deviceTotalBytes - eepromWriterInfo.startAddr))
    {
        printf ("The data file is too big to fit into the device.\n");
        return FALSE;
    }

    eepromWriterInfo.writeBytes = data_len;
    if (write_addr != WRITE_DATA_ADDRESS)
        write_addr = WRITE_DATA_ADDRESS;
    eepromWriterInfo.writeData  = (uint8_t *)write_addr;
    eepromWriterInfo.readData   = (uint8_t *)(write_addr + eepromWriterInfo.deviceTotalBytes);

    return TRUE;
}

/******************************************************************************
 * Function:    main
 ******************************************************************************/
void main ()
{
    FILE                    *fp;
    platform_init_flags     init_flags;
    platform_init_config    init_config;
    PLATFORM_DEVICE_info    *p_device;
    Bool                    ret;

    printf("EEPROM Writer Utility Version %s\n\n", version);

    fp = fopen(input_file, "r");
    if (fp == NULL)
    {
        printf("Error in opening %s input file\n", input_file);
        return;
    }

    ret = parse_input_file(fp);
    fclose (fp);

    if (ret == FALSE)
    {
        printf("Error in parsing %s input file\n", input_file);
        return;
    }

    /* Initialize main Platform lib */
    memset(&init_config, 0, sizeof(platform_init_config));
    memset(&init_flags, 0x01, sizeof(platform_init_flags));
    init_flags.pll = 0;
    init_flags.ddr = 0;
    init_flags.phy = 0;

    if (platform_init(&init_flags, &init_config) != Platform_EOK)
    {
        printf ("Platform init failed!\n");
        print_platform_errno();
        return;
    }

    p_device = platform_device_open(eepromWriterInfo.busAddr, 0);
    if (p_device == NULL)
    {
        printf ("EEPROM device open failed!\n");
        print_platform_errno();
        return;
    }
    eepromWriterInfo.deviceTotalBytes = p_device->block_count * p_device->page_count * p_device->page_size;

    /* Open and find the length of the data file */
    fp = fopen (eepromWriterInfo.file_name, "rb");
    if (fp == NULL)
    {
        printf ("Failed to open file %s\n", eepromWriterInfo.file_name);
        print_platform_errno();
        platform_device_close(p_device->handle);
        return;
    }

    /* Parse the CCS format file */
    ret = find_file_length(fp);
    fclose (fp);
    if (ret == FALSE)
    {
        printf("Error in parsing CCS file %s\n", eepromWriterInfo.file_name);
        platform_device_close(p_device->handle);
        return;
    }

    /* Write the EEPROM */
    if (flash_eeprom (p_device) == FALSE)
    {
        printf ("EEPROM write failed\n");
        platform_device_close(p_device->handle);
        return;
    }

    /* verify the flash */
    if(flash_verify (p_device) == FALSE)
    {
        printf ("EEPROM read verify failed\n");
        platform_device_close(p_device->handle);
        return;
    }


    printf ("EEPROM programming completed successfully\n");

    platform_device_close(p_device->handle);

    return;
}




